package automaton;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.*;

import automaton.rule.*;
import automaton.view.*;


/**
 * Correasponde a um automato celular de uma dimensao (modelado como um vetor de booleanos) e 
 * o desenho deste em um JFrame Swing.
 * 
 * O Automato possui dois estados: On = 1 ou Off = 0.
 * 
 * A classe herda JFrame porque desenha o automato. No desenho, cada automato eh representado 
 * por um quadrado preenchido se este estiver no estado On em branco se estiver Off.
 * 
 * @author Grupo 6
 */
public class AutomatoCelular1D extends JFrame
							   implements ChangeListener, ActionListener{	

	/**
	 * Serial
	 */
	private static final long serialVersionUID = -7812049474520973526L;

	/**
	 * Tamanho do vetor de automatos
	 */
	private static final int TAMANHO_AUTOMATO_1D = 256;

	
	/**
	 * Regras que podem ser selecionadas
	 */
	private static final int REGRA_54 = 0;
	private static final int REGRA_126 = 1;
	private static final int REGRA_169 = 2;
	private static final int REGRA_250 = 3;
	private static final int REGRA_DIST_2 = 4;
	private static final int REGRA_DIST_4 = 5;
	
	/**
	 * Texto de titulo da janela
	 */
	private static final String TEXTO_DE_TITULO = "Automato 1D";
	
	/**
	 * Matriz que contem todas as geracoes de automatos que devem ser desenhadas na tela
	 * Os automatos podem assumir estado 0 = Off ou 1=On, portanto sao modelados pelo tipo boolean.
	 */
	BooleanMatrix matrizTemporalAutomatos;
	
	/**
	 * Tempo em milissegundos para incrementar a geracao. 
	 * Deve ser configuravel pela tela.
	 */
	private int delayTempo;	
	
	/**
	 * True se o simulador esta funcionando.
	 */
	private boolean ligado;
	
	/**
	 * Codigo da regra em que o automato deve funcionar
	 */
	private AutomatoCelularRegra regra;
	
	
	// -- Componentes da tela para posicionamento dos elementos (botoes, labels, panels etc) --//
	JPanel panPainelDeBaixo;
	BooleanMatrixCanvas canvasMatriz;
	JSlider sldVelocidade;
	JLabel lblVelocidadeRapido, lblVelocidadeLento;
	JButton btnAtualizar;
	JComboBox lstRegras;
	
	/**
	 * Construtor
	 * Apenas instancia a matriz de booleanos e o frame de desenho
	 */
	public AutomatoCelular1D()
	{
		super(AutomatoCelular1D.TEXTO_DE_TITULO);
		this.setSize(800, 600); // dimensao padrao  = 800x600
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);	
		this.setLigado(false);
		this.setDelayTempo(60);  // fps inicial =~ 17 fps
		this.setup();
		this.atualizarRegra();
	}
	
	/**
	 * Metodo responsavel por criar o componentes Swing e por adiciona-los a este JFrame.
	 */
	private void setup()
	{
		this.setLayout(new BorderLayout());
		
		// Painel de baixo que contem as definicoes de velocidade e de regra
			this.panPainelDeBaixo = new JPanel();
			this.panPainelDeBaixo.setPreferredSize(new Dimension(800,60));
			this.panPainelDeBaixo.setLayout(new FlowLayout()); 
			// O que for colocado dentro do painel vai respeitar a regra do FlowLayout, 
			// nao do Border como no elemento pai, ou seja, nao serao esticados no 
			// espaco que puderem ocupar, ocuparao o minimo necessario e ficarao um ao lado
			// do outro
			this.add(panPainelDeBaixo, BorderLayout.SOUTH);
		
		// Slider que define velocidade de animacao
			lblVelocidadeRapido = new JLabel("Rapido < ");
			panPainelDeBaixo.add(lblVelocidadeRapido);
			
			sldVelocidade = new JSlider(SwingConstants.HORIZONTAL);
			sldVelocidade.setValue(this.getDelayTempo());
			sldVelocidade.setMinimum(10);
			sldVelocidade.setMaximum(110);
			sldVelocidade.addChangeListener(this);
			sldVelocidade.setPreferredSize(new Dimension(this.getWidth()-400,50));
			panPainelDeBaixo.add(sldVelocidade);
			
			lblVelocidadeLento = new JLabel(" > Lento");
			panPainelDeBaixo.add(lblVelocidadeLento);
		
		// Area de desenho do automato celular
			
			// inicializando com null pois a matriz sera passada para o canvas qndo for instanciada, no metodo atualizarRegra
			canvasMatriz = new BooleanMatrixCanvas(null); 
			this.add(canvasMatriz, BorderLayout.NORTH);
			canvasMatriz.setSize(this.getWidth(),this.getHeight()-panPainelDeBaixo.getHeight());
		
		// ComboBox da regra
			String[] data = {"Regra 54","Regra 126","Regra 169","Regra 250","Vizinhanca Distancia = 2", "Vizinhanca Distancia = 4"};
			lstRegras = new JComboBox(data);
			lstRegras.setName("lstRegras");
			lstRegras.addActionListener(this);
			panPainelDeBaixo.add(lstRegras);
	}
	
	/**
	 * Instancia e atualiza o atributo regra de acordo com o item que estiver selecionado
	 * no comboBox lstRegras.
	 */
	private void atualizarRegra()
	{
		this.matrizTemporalAutomatos = new BooleanMatrix(new Dimension(1,AutomatoCelular1D.TAMANHO_AUTOMATO_1D));
		this.matrizTemporalAutomatos.setCell(0,AutomatoCelular1D.TAMANHO_AUTOMATO_1D/2,true);
		this.canvasMatriz.setMatriz(this.matrizTemporalAutomatos);
		
		switch(lstRegras.getSelectedIndex())
		{
			case REGRA_54:
				this.regra = new AutomatoCelularRegra54(this.matrizTemporalAutomatos);
			break;
			
			case REGRA_126:
				this.regra = new AutomatoCelularRegra126(this.matrizTemporalAutomatos);
			break;
			
			case REGRA_169:
				this.regra = new AutomatoCelularRegra169(this.matrizTemporalAutomatos);
			break;
			
			case REGRA_250:
				this.regra = new AutomatoCelularRegra250(this.matrizTemporalAutomatos);
			break;
			
			case REGRA_DIST_2:
				this.regra = new AutomatoCelularRegraDistancia2(matrizTemporalAutomatos);
			break;
			
			case REGRA_DIST_4:
				this.regra = new AutomatoCelularRegraDistancia4(matrizTemporalAutomatos);
			break;
		}
	}
	

	/**
	 * Cria o loop de execucao do automato celular incrementando
	 * a geracao do automato quando o form termina de ser desenhado.
	 * 
	 * O desenho � feito com Active Rendering, adaptado a partir
	 * do esquema descrito no link {@link http://www.gamedev.net/page/resources/_/technical/general-programming/java-games-active-rendering-r2418}
	 * 
	 * @throws InterruptedException 
	 */
	private void disparar()
	{
		while(this.isLigado())
		{			
			try
			{
				Thread.sleep(this.getDelayTempo());
			} catch(Exception exc) {
				System.out.print("loop error: "+exc.getMessage());
			}
			this.regra.incrementarGeracao();
			this.canvasMatriz.repaint();
		}
	}
	
	
	/**
	 * Exibe a tela e inicia a execucao  do automato celular
	 */
	public void run()
	{	
		this.setLigado(true);
		this.setVisible(true);
		this.disparar();
	}
	
	// --- Getters and setters --- //
	
	/**
	 * @return the delayTempo
	 */
	public int getDelayTempo() {
		return delayTempo;
	}

	/**
	 * @param delayTempo the delayTempo to set
	 */
	public void setDelayTempo(int delayTempo) {
		this.delayTempo = delayTempo;
	}

	/**
	 * @return the ligado
	 */
	public boolean isLigado() {
		return ligado;
	}

	/**
	 * @param ligado the ligado to set
	 */
	public void setLigado(boolean ligado) {
		this.ligado = ligado;
	}

	// -- Tratamento de eventos -- //
		
		public void stateChanged(ChangeEvent e) {
			
			// Atualizando velocidade segundo o slider
			int lVelocidade = sldVelocidade.getValue();
			this.setDelayTempo(lVelocidade);
			
			
		}
	
		public void actionPerformed(ActionEvent e) {
			if(e.getSource() == this.lstRegras)
			{
				this.atualizarRegra();
			}
		}
}
